/*
 * This program is free software; you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation; either version 3 of the License, or 
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
 * for more details.
 * 
 * You should have received a copy of the GNU General Public License along 
 * with this program; if not, write to the Free Software Foundation, Inc., 
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include <QSettings>
#include <QTimer>
#include <QRegExp>
#include "board.h"
#include "chan.h"
#include "log.h"
#include "thread.h"
#include "chanmanager.h"
#include "post.h"

namespace Wishmaster
{

	Board::Board(QString const& name, Chan* parent) : QObject(), m_name(name),
		m_parent(parent), m_threadStorage(NULL)
	{
		m_visibleThreads = new EntityCollector<ThreadPtr, ThreadId>();
		m_hiddenThreads = new EntityCollector<ThreadPtr, ThreadId>();
		m_archivedThreads = new EntityCollector<ThreadPtr, ThreadId>();
	}

	Board::~Board()
	{
		visibleThreads()->clear();
	}

	void Board::saveSettings()
	{
		QSettings settings;
		QString prefix = QString("boardsettings/") + getParent()->getHost() + name();
		if(property("override").toBool())
		{
			settings.setValue(prefix + "override", true);
			settings.setValue(prefix + "indexonly", property("indexonly"));
			settings.setValue(prefix + "refreshinterval", property("refreshinterval"));
			settings.setValue(prefix + "donotrefresh", property("donotrefresh"));
		}
		else
		{
			settings.setValue(prefix + "override", false);
		}
	}

	void Board::loadSettings()
	{
		QSettings settings;
		QString prefix = QString("boardsettings/") + m_parent->getHost() + name();
		if(settings.value(prefix + "override").toBool())
		{
			setProperty("override", true);
			setProperty("indexonly", settings.value(prefix + "indexonly"));
			setProperty("refreshinterval", settings.value(prefix + "refreshinterval"));
			setProperty("donotrefresh", settings.value(prefix + "donotrefresh"));
		}
		else
		{
			setProperty("override", false);
		}
		//settingsChanged();
	}

	Chan* Board::getParent()
	{
		return m_parent;
	}

	void Board::setParent(Chan* parent)
	{
		m_parent = parent;
	}

	ThreadPtr Board::getThread(const ThreadId& threadID)
	{
		ThreadPtr thread = visibleThreads()->lookup(threadID);
		if(!thread)
		{
			thread = hiddenThreads()->lookup(threadID);
			if(!thread)
				thread = archivedThreads()->lookup(threadID);
		}
		return thread;
	}

	void Board::sort()
	{
		visibleThreads()->sort();
	}

	PostId Board::getPostID(const QString& link, const ThreadId& thread_id)
	{
		return m_parser->getPostID(link, thread_id);
	}

	BoardParser* Board::parser()
	{
		return m_parser;
	}

	void Board::setParser(BoardParser* p)
	{
		m_parser = p;
	}

	int Board::threadPosition(const ThreadId& threadId)
	{
		return visibleThreads()->getPosition(threadId);
	}

	void Board::setAlias(const QString& alias)
	{
		m_alias = alias;
	}

	QString Board::alias()
	{
		return m_alias;
	}

	void Board::setThreadStorage(ThreadStorage* threadstorage)
	{
		m_threadStorage = threadstorage;
	}

	ThreadStorage* Board::threadStorage()
	{
		return m_threadStorage;
	}

	void Board::hideThread(const ThreadId& thread_id)
	{
		ThreadPtr thr = visibleThreads()->pick(thread_id);
		if(thr.isNull())
			return;
		thr->setVisibility(Hide);
		hiddenThreads()->push_back(thr);
	}

	void Board::removeThread(const ThreadId& thread_id)
	{
		ThreadPtr thread = visibleThreads()->pick(thread_id);
		// thread will be destroyed here
	}

	bool Board::isThreadHidden(const ThreadId& thread_id)
	{
		return (hiddenThreads()->lookup(thread_id).isNull());
	}

	void Board::archiveThread(const ThreadId& thread_id)
	{
		ThreadPtr thr = visibleThreads()->pick(thread_id);
		if(!thr.isNull())
		{
			thr->setFlag(Thread::Archived, true);
			archivedThreads()->push_back(thr);
		}
	}

	void Board::save()
	{
		log(llDebug, "Board::save() %s", qPrintable(name()));
		for(threadIterator it = visibleThreads()->begin(); it != visibleThreads()->end(); ++it)
		{
			ThreadPtr thread = *it;
			if((!thread->flag(Thread::Swapped)) && (thread->getActualPostsCount() > 0))
				thread->swap();
		}

		for(threadIterator it = hiddenThreads()->begin(); it != hiddenThreads()->end(); ++it)
		{
			(*it)->swap();
		}

		for(threadIterator it = archivedThreads()->begin(); it != archivedThreads()->end(); ++it)
		{
			(*it)->swap();
		}
	}

	void Board::load()
	{
		QList<unsigned long> threads = m_threadStorage->threadList(m_parent->getHost(), name());
		foreach(unsigned long thread_entry_id, threads)
		{
			ThreadPtr thread = m_threadStorage->restoreThread(this, thread_entry_id, false);
			if(thread->visibility() != Hide &&
			   thread->visibility() != HideForever)
			{
				if(thread->flag(Thread::Archived))
					archivedThreads()->push_back(thread);
				else
					visibleThreads()->push_back(thread);
			}
			else
				hiddenThreads()->push_back(thread);
		}
	}

	void Board::swapAll()
	{
		for(threadIterator it = visibleThreads()->begin(); it != visibleThreads()->end(); ++it)
		{
			ThreadPtr thread = *it;
			if((!thread->flag(Thread::Swapped)) && (thread->getActualPostsCount() > 0))
			{
				thread->swap();
			}
		}

		for(threadIterator it = archivedThreads()->begin(); it != archivedThreads()->end(); ++it)
		{
			ThreadPtr thread = *it;
			if((!thread->flag(Thread::Swapped)) && (thread->getActualPostsCount() > 0))
			{
				thread->swap();
			}
		}
	}

	void Board::swap(const QDateTime& current, int interval, EntityCollector<ThreadPtr, ThreadId>* collector)
	{
		for(threadIterator it = collector->begin(); it != collector->end(); ++it)
		{
			ThreadPtr thread = *it;
			//log(llDebug, "[%d]refcount: %d", thread->id(), thread.refCount());
			// 2 links in collector, +1 holds this pointer
			if((!thread->flag(Thread::Swapped)) && (thread->getActualPostsCount() > 0) && (thread.refCount() <= 3))
			{
				QDateTime ts = thread->getTimestamp();
				//log(llDebug, "Swapping thread: %d/%d/%d", thread->id().threadNumericId(), ts.secsTo(current), interval);
				if(ts.secsTo(current) > interval)
				{
					thread->swap();
				}
			}
		}
	}

	void Board::swap(const QDateTime& current, int interval)
	{
		swap(current, interval, visibleThreads());
		for(threadIterator it = hiddenThreads()->begin(); it != hiddenThreads()->end(); ++it)
		{
			(*it)->swap();
		}
		swap(current, interval, archivedThreads());
	}

	void Board::collectGarbage()
	{
		QSettings settings;
		m_threadStorage->setMaxThreads(settings.value("threadstorage/keepthreads", 100).toInt()); // This may have been changed
		QList<unsigned long> deleted = m_threadStorage->deleteOldThreads(getParent()->getHost(), name());
		foreach(unsigned long id, deleted)
		{
			ThreadPtr t = visibleThreads()->pick(ThreadId(getParent()->getHost(), name(), id));
			// TODO collect hidden threads
		}
	}

	EntityCollector<ThreadPtr, ThreadId>* Board::visibleThreads() const
	{
		return m_visibleThreads;
	}

	EntityCollector<ThreadPtr, ThreadId>* Board::hiddenThreads() const
	{
		return m_hiddenThreads;
	}

	EntityCollector<ThreadPtr, ThreadId>* Board::archivedThreads() const
	{
		return m_archivedThreads;
	}

	BoardConstraints Board::constraints()
	{
		return m_constraints;
	}

	void Board::setConstraints(const BoardConstraints& constraints)
	{
		m_constraints = constraints;
	}

	void Board::shiftThreads()
	{
		for(threadIterator it = visibleThreads()->begin(); it != visibleThreads()->end(); ++it)
		{
			ThreadPtr thread = *it;
			thread->setPage(thread->page() + 1);
		}
	}

	FilterListPtr Board::filters()
	{
		return FilterListPtr();
	}
}

